package icraf.carbon.core;

import icraf.carbon.gui.MenuFactory.MenuProperty;
import icraf.carbon.model.ModelProject;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

//TODO: beberapa label masi blom local wise
public class Project implements AbacusContainer {

	class SummaryData extends Summary {
		double sumEmit = 0;
		double sumEmitEli = 0;
		double sumSeq = 0;
		double sumSeqEli = 0;
		HashMap<CostBenefitUnit, Double> sumCostB = new HashMap<CostBenefitUnit, Double>();
		HashMap<CostBenefitUnit, Double> sumCostBEli = new HashMap<CostBenefitUnit, Double>();

		@Override
		public double getEmissionEligiblePerHa() {
			return sumEmitEli;
		}

		@Override
		public double getEmissionEligiblePerYear() {
			return getEmissionEligiblePerHa() * getTotalArea();
		}

		@Override
		public double getEmissionPerHa() {
			return sumEmit;
		}

		@Override
		public double getEmissionPerYear() {
			return getEmissionPerHa() * getTotalArea();
		}

		@Override
		public double getSequestrationEligiblePerHa() {
			return Math.abs(sumSeqEli);
		}

		@Override
		public double getSequestrationEligiblePerYear() {
			return Math.abs(getSequestrationEligiblePerHa() * getTotalArea());
		}

		@Override
		public double getSequestrationPerHa() {
			return Math.abs(sumSeq);
		}

		@Override
		public double getSequestrationPerYear() {
			return Math.abs(getSequestrationPerHa() * getTotalArea());
		}

		@Override
		public double getTotalCostBenefitPerHa(CostBenefitUnit cbu) {
			if(cbu == null) return Double.NaN;
			Double d = sumCostB.get(cbu);
			if(d == null) return Double.NaN;
			return d;
		}

		@Override
		public double getTotalCostBenefitPerYear(CostBenefitUnit cbu) {
			if(cbu == null) return Double.NaN;
			Double d = sumCostB.get(cbu);
			if(d == null) return Double.NaN;
			return d* getTotalArea();
		}

		@Override
		public double getTotalCostBenefitEligiblePerHa(CostBenefitUnit cbu) {
			if(cbu == null) return Double.NaN;
			Double d = sumCostBEli.get(cbu);
			if(d == null) return Double.NaN;
			return d;
		}

		@Override
		public double getTotalCostBenefitEligiblePerYear(CostBenefitUnit cbu) {
			if(cbu == null) return Double.NaN;
			Double d = sumCostBEli.get(cbu);
			if(d == null) return Double.NaN;
			return d* getTotalArea();
		}

		@Override
		public CostBenefitUnit[] getCostBenefitUnits() {
			return getCostBenefitUnitList().toArray(new CostBenefitUnit[getCostBenefitUnitList().size()]);
		}

		@Override
		public double getNetEmissionEligiblePerYear() {
			return getEmissionEligiblePerYear()-getSequestrationEligiblePerYear();
		}

		@Override
		public double getNetEmissionEligiblePerHa() {
			return getEmissionEligiblePerHa()-getSequestrationEligiblePerHa();
		}

		@Override
		public double getNetEmissionPerYear() {
			return getEmissionPerYear()-getSequestrationPerYear();
		}

		@Override
		public double getNetEmissionPerHa() {
			return getEmissionPerHa()-getSequestrationPerHa();
		}

	}
	
	public static CostBenefitUnit defaultCostBenefitUnit1 = new CostBenefitUnit(
			"Private", "Net return received by the land-use operator, farmers");
	public static CostBenefitUnit defaultCostBenefitUnit2 = new CostBenefitUnit(
			"Social", "Economic efficiency at national scale");
	public static CostBenefitUnit defaultCostBenefitUnit[] = {
			defaultCostBenefitUnit1, defaultCostBenefitUnit2 };
	
	private DataComparator comparator = new DataComparator();
	private ArrayList<CostBenefitUnit> costBenefitUnitList = new ArrayList<CostBenefitUnit>();
	private HashMap<CostBenefitUnit, ArrayList<DataItem>> data = new HashMap<CostBenefitUnit, ArrayList<DataItem>>();
	private String description = "Project description";
	private int durationTime = 1; // year
	private HashMap<LandCover, HashMap<LandCover, Boolean>> eligibility = new HashMap<LandCover, HashMap<LandCover, Boolean>>();
	private double[] emissionCum;
	private HashMap<CostBenefitUnit, Integer> indexMaxNegativeNPV = new HashMap<CostBenefitUnit, Integer>();
	private int indexMinEmission;
	private HashMap<CostBenefitUnit, Integer> indexMinNegativeNPV = new HashMap<CostBenefitUnit, Integer>();
	private int indexMinSequestration;
	
	private boolean isIncludeBelowgroundEmission = false;
	private boolean isIncludeModifiedEmission = false;
	private boolean isUsingBelowgroundFactor = true;
	private boolean isUsingModifiedFactor = true;
	
	private String label = "Project";
	private LandCoverList landCoverList = new LandCoverList();
	private ModelProject modelProject;
	private SummaryData summaryData = new SummaryData();
	private double totalArea; // Ha
	private ZoneList zoneList = new ZoneList(this);

	public Project() {
		zoneList.addNew();
		
		landCoverList.add(new LandCover(MenuProperty.PROJECT_LANDUSE.getLabel()+" 1"));
		costBenefitUnitList.add(defaultCostBenefitUnit1);
	}

	public Project(int nZone) {
		setNumberOfZone(nZone);
		landCoverList.add(new LandCover(MenuProperty.PROJECT_LANDUSE.getLabel()+" 1"));
		costBenefitUnitList.add(defaultCostBenefitUnit1);
	}

	private int findCenterIndex() {
		CostBenefitUnit defCBU = costBenefitUnitList.get(0);
		if (data.get(defCBU).size() <= 2) {
			return 0;
		}
		boolean notfound = true;
		int ci = data.get(defCBU).size() / 2;
		int min = 0;

		while (notfound) {
			double e = data.get(defCBU).get(ci).getEmission();
			if (e > 0) {
				if (ci == 0) {
					return 0;
				}
				if (data.get(defCBU).get(ci - 1).getEmission() <= 0) {
					notfound = false;
				} else {
					ci = min + (ci - min) / 2;
				}
			} else {
				if (ci >= data.get(defCBU).size()-1)
					return ci;
				if (data.get(defCBU).get(ci + 1).getEmission() > 0) {
					notfound = false;
				} else {
					min = ci;
					ci = ci + (data.get(defCBU).size() - ci) / 2;
				}
			}
		}
		return ci;
	}

	public void generateSimulationModel(int nIteration) {
		if (modelProject == null)
			modelProject = new ModelProject(this);
		modelProject.runSimulationModel(nIteration);
	}
	
	public double getSimulationProgress() {
		return modelProject.getProgress();
	}

	@Override
	public OpcostData getAbacusData(int index, CostBenefitUnit costBenefitUnit) {
		if (costBenefitUnit == null)
			return null;
		return data.get(costBenefitUnit).get(index);
	}

	public ArrayList<OpcostData> getAvoidedEmissionList(double treshold,
			CostBenefitUnit costBenefitUnit, boolean isEligibleOnly) {
		if (costBenefitUnit == null)
			return null;
		ArrayList<DataItem> d = data.get(costBenefitUnit);
		ArrayList<OpcostData> arr = new ArrayList<OpcostData>();
		for (int i = indexMinEmission; i < d.size(); i++) {
			if (isEligibleOnly && !d.get(i).isEligible())
				continue;
			if (d.get(i).getCost(costBenefitUnit) > treshold)
				return arr;
			arr.add(d.get(i));
		}
		return arr;
	}

	public ArrayList<CostBenefitUnit> getCostBenefitUnitList() {
		return costBenefitUnitList;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see icraf.carbon.core.AbacusContainer#getCumulativeEmission(int)
	 */
	@Override
	public double getCumulativeEmission(int index) {
		if (emissionCum == null || index < 0 || index >= emissionCum.length)
			return Double.NaN;
		return emissionCum[index];
	}

	@Override
	public AbacusData getDataMaxEmission(CostBenefitUnit costBenefitUnit) {
		return data.get(costBenefitUnit).get(
				data.get(costBenefitUnit).size() - 1);
	}

	@Override
	public AbacusData getDataMaxSequestration(CostBenefitUnit costBenefitUnit) {
		return data.get(costBenefitUnit).get(0);
	}

	@Override
	public AbacusData getDataMinEmission(CostBenefitUnit costBenefitUnit) {
		return data.get(costBenefitUnit).get(indexMinEmission);
	}

	@Override
	public AbacusData getDataMinSequestration(CostBenefitUnit costBenefitUnit) {
		return data.get(costBenefitUnit).get(indexMinSequestration);
	}

	@Override
	public int getDataSize(CostBenefitUnit costBenefitUnit) {
		return data.get(costBenefitUnit).size();
	}

	public String getDescription() {
		return description;
	}

	public float getDurationTime() {
		return durationTime;
	}

	@Override
	public int getIndexMaxSequestrationNegativeCost(
			CostBenefitUnit costBenefitUnit) {
		return indexMinNegativeNPV.get(costBenefitUnit);
	}

	@Override
	public int getIndexMinEmission() {
		return indexMinEmission;
	}

	public String getLabel() {
		return label;
	}

	public LandCoverList getLandCoverList() {
		return landCoverList;
	}

	public ModelProject getModelProject() {
		return modelProject;
	}

	public int getNumberOfZone() {
		return zoneList.getSize();
	}

	public Summary getSummary() {
		summaryData.sumEmit = 0;
		summaryData.sumSeq = 0;
		summaryData.sumEmitEli = 0;
		summaryData.sumSeqEli = 0;
		summaryData.sumCostB.clear();
		summaryData.sumCostBEli.clear();
		ArrayList<CostBenefitUnit> cbuList = getCostBenefitUnitList();
		for (int i = 0; i < zoneList.getSize(); i++) {
			Zone z = zoneList.get(i);
			for (LandCover lc1 : landCoverList) {
				for (LandCover lc2 : landCoverList) {
					double d = z.getEmission(lc1, lc2);
					if (d > 0) {
						summaryData.sumEmit += d;
						if (z.isEligible() && isEligible(lc1, lc2)) {
							summaryData.sumEmitEli += d;
						}
					} else {
						summaryData.sumSeq += d;
						if (z.isEligible() && isEligible(lc1, lc2)) {
							summaryData.sumSeqEli += d;
						}
					}
					for(CostBenefitUnit cbu: cbuList) {
						double v = z.getCostBenefitTotal(lc1, lc2, cbu);
						Double sumV = summaryData.sumCostB.get(cbu);
						sumV =(sumV == null)? v:sumV+v;
						summaryData.sumCostB.put(cbu, sumV);
						if (z.isEligible() && isEligible(lc1, lc2)) {
							Double sumVe = summaryData.sumCostBEli.get(cbu);
							sumVe =(sumVe == null)? v:sumVe+v;
							summaryData.sumCostBEli.put(cbu, sumVe);
						}
					}
					
				}
			}
		}

		return summaryData;
	}

	@Override
	public double getTotalArea() {
		return totalArea;
	}

	@Override
	public double getTotalEmission() {
		if (emissionCum == null)
			return Double.NaN;
		return emissionCum[emissionCum.length - 1];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * icraf.carbon.core.AbacusContainer#getCumulativeEmissionByCost(double)
	 */
	@Override
	public double getTotalEmissionByCost(double treshold,
			CostBenefitUnit costBenefitUnit) {
		// TODO: biar lebih cepet, pakai methode setengah kayak "find center"!
		if (costBenefitUnit == null)
			return 0;
		ArrayList<DataItem> d = data.get(costBenefitUnit);
		if (treshold > d.get(d.size() - 1).getCost(costBenefitUnit))
			return emissionCum[d.size() - 1];
		if (indexMaxNegativeNPV.get(costBenefitUnit) == null)
			return 0;
		for (int i = indexMaxNegativeNPV.get(costBenefitUnit) + 1; i < d.size(); i++) {
			if (d.get(i).getCost(costBenefitUnit) > treshold)
				return emissionCum[i - 1];
		}
		return 0;
	}

	@Override
	public double getTotalEmissionNegativeCost(CostBenefitUnit costBenefitUnit) {
		if (emissionCum == null
				|| indexMaxNegativeNPV.get(costBenefitUnit) == null)
			return Double.NaN;
		return emissionCum[indexMaxNegativeNPV.get(costBenefitUnit)];
	}

	@Override
	public double getTotalSequestration() {
		if (emissionCum == null)
			return Double.NaN;
		return emissionCum[0];
	}

	@Override
	public double getTotalSequestrationNegativeCost(
			CostBenefitUnit costBenefitUnit) {
		if (emissionCum == null
				|| indexMaxNegativeNPV.get(costBenefitUnit) == null)
			return Double.NaN;
		return emissionCum[indexMinNegativeNPV.get(costBenefitUnit)];
	}

	public void getValue() {
	}


	public ZoneList getZoneList() {
		return zoneList;
	}

	public boolean isEligible(LandCover lc1, LandCover lc2) {
		HashMap<LandCover, Boolean> lc1List = eligibility.get(lc1);
		if (lc1List == null)
			return true;
		if (lc1List.get(lc2) == null)
			return true;
		return lc1List.get(lc2);
	}

	public boolean isIncludeBelowgroundEmission() {
		return isIncludeBelowgroundEmission;
	}

	protected void setCostBenefitUnitList(
			ArrayList<CostBenefitUnit> costBenefitUnitList) {
		this.costBenefitUnitList = costBenefitUnitList;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public void setEligibility(boolean isEligible, LandCover lc1, LandCover lc2) {
		HashMap<LandCover, Boolean> lc1List = eligibility.get(lc1);
		if (lc1List == null) {
			lc1List = new HashMap<LandCover, Boolean>();
		}
		lc1List.put(lc2, isEligible);
		eligibility.put(lc1, lc1List);
	}

	public void setIncludeBelowgroundEmission(
			boolean isIncludeBelowgroundEmission) {
		this.isIncludeBelowgroundEmission = isIncludeBelowgroundEmission;
	}

	public void setLabel(String label) {
		this.label = label;
	}

	protected void setLandCoverList(LandCoverList landCoverList) {
		this.landCoverList = landCoverList;
	}

	protected void setNumberOfZone(int nZone) {
		zoneList.clear();
		for (int i = 0; i < nZone; i++) {
			Zone z = new Zone("Zone" + (i + 1));
			z.setProject(this);
			zoneList.add(z);
		}
	}

	public void setTime(int time) {
		this.durationTime = time;
	}

	public void setTotalArea(double totalArea) {
		this.totalArea = Double.isNaN(totalArea) ? 0 : totalArea;
	}

	@Override
	public String toString() {
		return getLabel();
	}

	public void updateDataArray() {
		updateDataArray(false);
	}
	
	private ArrayList<Zone> dataZoneList = new ArrayList<Zone>();
	
	@Override
	public ArrayList<Zone> getDataZoneList() {
		return dataZoneList;
	}

	public void updateDataArray(boolean isEligibleOnly) {
		data.clear();
		dataZoneList.clear();
		for (int i = 0; i < zoneList.getSize(); i++) {
			final Zone z = zoneList.get(i);
			if(isEligibleOnly ) {
				if( z.isEligible())
					dataZoneList.add(z);
			} else {
				dataZoneList.add(z);
			}
			for (final LandCover lc1 : landCoverList) {
				for (final LandCover lc2 : landCoverList) {
					if(isEligibleOnly && !(z.isEligible() && isEligible(lc1, lc2)))
						continue;
					
					DataItem d = new DataItem(z, lc1, lc2);
					
					// TODO: eligible?
					for (CostBenefitUnit cbu : costBenefitUnitList) {
						ArrayList<DataItem> dataList = data.get(cbu);
						if (dataList == null)
							dataList = new ArrayList<DataItem>();
						dataList.add(d);
						data.put(cbu, dataList);
					}
				}
			}
		}
		updateEmissionCum();
	}

	private void updateEmissionCum() {
		// default cbu
		CostBenefitUnit defCBU = costBenefitUnitList.get(0);
		if (data.get(defCBU).size() <= 0) {
			return;
		}
		comparator.setAbsolute(false);
		comparator.setAscending(true);

		for (CostBenefitUnit cbu : costBenefitUnitList) {
			comparator.setCostBenefitUnit(cbu);
			Collections.sort(data.get(cbu), comparator);
		}

		// emission data
		// yMaxEmission = 0;
		indexMinEmission = findCenterIndex();
		indexMinSequestration = indexMinEmission;

		for (int i = indexMinEmission - 1; i >= 0; i--) {
			if (data.get(defCBU).get(i).getCost(defCBU) != 0) {
				indexMinSequestration = i;
				break;
			}
		}

		int arrSize = data.get(defCBU).size();
		emissionCum = new double[arrSize];
		emissionCum[indexMinEmission] = data.get(defCBU).get(indexMinEmission)
				.getEmission();
		for (CostBenefitUnit cbu : costBenefitUnitList) {
			boolean flag = true;
			for (int i = indexMinEmission + 1; i < arrSize; i++) {
				emissionCum[i] = emissionCum[i - 1]
						+ data.get(defCBU).get(i).getEmission();

				if (flag
						&& (Math.signum(data.get(cbu).get(i).getCost(cbu)) > 0)) {
					indexMaxNegativeNPV.put(cbu, i - 1);
					flag = false;
				}

			}

			// sequestration data
			flag = true;
			// yMaxSequestration = 0;
			if (indexMinEmission > 0) {
				emissionCum[indexMinEmission - 1] = data.get(defCBU)
						.get(indexMinEmission - 1).getEmission();
				for (int i = indexMinEmission - 2; i >= 0; i--) {
					emissionCum[i] = emissionCum[i + 1]
							+ data.get(defCBU).get(i).getEmission();

					if (flag
							&& (Math.signum(data.get(cbu).get(i).getCost(cbu)) < 0)) {
						indexMinNegativeNPV.put(cbu, i + 1);
						flag = false;
					}
				}
			}

		}

		// System.out.println(Arrays.toString(emissionCum));
	}

	public boolean isIncludeModifiedEmission() {
		return isIncludeModifiedEmission;
	}

	public void setIncludeModifiedEmission(boolean isIncludeModifiedEmission) {
		this.isIncludeModifiedEmission = isIncludeModifiedEmission;
	}

	public boolean isUsingBelowgroundFactor() {
		return isUsingBelowgroundFactor;
	}

	public void setUsingBelowgroundFactor(boolean isUsingBelowgroundFactor) {
		this.isUsingBelowgroundFactor = isUsingBelowgroundFactor;
	}

	public boolean isUsingModifiedFactor() {
		return isUsingModifiedFactor;
	}

	public void setUsingModifiedFactor(boolean isUsingModifiedFactor) {
		this.isUsingModifiedFactor = isUsingModifiedFactor;
	}
}
